#include "hash.h"

/// ------------------------------------------------------------------------ murmur3

static mx_inline mx_uint32_t rotl32 (mx_uint32_t x, mx_int32_t r)
{
    return (x << r) | (x >> (32 - r));
}

// MurmurHash3 32-bit 主函数
mx_uint32_t hash_murmur3_32 (mx_uint8_pt data, mx_uint32_t len, mx_uint32_t seed)
{
#define __c1 0xcc9e2d51
#define __c2 0x1b873593
    mx_uint32_t h1 = seed;
    mx_uint32_t k1 = 0;
    /* block processing */
    mx_int32_t nblocks = len / 4;
    for (mx_int32_t i = 0; i < nblocks; i++)
    {
        k1  = ((mx_uint32_t)data[i * 4 + 0]);
        k1 |= ((mx_uint32_t)data[i * 4 + 1]) << 8;
        k1 |= ((mx_uint32_t)data[i * 4 + 2]) << 16;
        k1 |= ((mx_uint32_t)data[i * 4 + 3]) << 24;
        k1 *= __c1;
        k1 = rotl32(k1, 15);
        k1 *= __c2;
        h1 ^= k1;
        h1 = rotl32(h1, 13);
        h1 = h1 * 5 + 0xe6546b64;
    }
    /* end processing */
    k1 = 0;
    mx_uint8_pt tail = data + nblocks * 4;
    switch (len & 3)
    {
        case 3:
            k1 ^= tail[2] << 16;
        case 2:
            k1 ^= tail[1] << 8;
        case 1:
            k1 ^= tail[0];
            k1 *= __c1;
            k1 = rotl32(k1, 15);
            k1 *= __c2;
            h1 ^= k1;
    }
    /* final mix */
    h1 ^= len;
    h1 ^= h1 >> 16;
    h1 *= 0x85ebca6b;
    h1 ^= h1 >> 13;
    h1 *= 0xc2b2ae35;
    h1 ^= h1 >> 16;
    return h1;
}

/// ------------------------------------------------------------------------ xor

mx_uint8_t hash_xor8 (mx_uint8_pt data, mx_usize_t len)
{
    mx_uint8_t acc = 0;
    for (mx_usize_t i = 0; i < len; i++)
    {
        acc ^= data[i];
    }
    return acc;
}

mx_uint16_t hash_xor16_le (mx_uint8_pt data, mx_usize_t len)
{
    mx_uint16_t acc = 0;
    mx_usize_t i = 0;
    for (; i + 1 < len; i += 2)
    {
        mx_uint16_t w = (mx_uint16_t)data[i] | ((mx_uint16_t)data[i + 1] << 8);
        acc ^= w;
    }
    if (i < len)
    {
        mx_uint16_t w = (mx_uint16_t)data[i];
        acc ^= w;
    }
    return acc;
}

mx_uint16_t hash_xor16_be (mx_uint8_pt data, mx_usize_t len)
{
    mx_uint16_t acc = 0;
    mx_usize_t i = 0;
    for (; i + 1 < len; i += 2)
    {
        mx_uint16_t w = ((mx_uint16_t)data[i] << 8) | (mx_uint16_t)data[i + 1];
        acc ^= w;
    }
    if (i < len)
    {
        mx_uint16_t w = ((mx_uint16_t)data[i] << 8);
        acc ^= w;
    }
    return acc;
}

/// ------------------------------------------------------------------------ crc

static mx_inline mx_uint8_t reflect8 (mx_uint8_t x)
{
    x = (mx_uint8_t)(((x & 0x55U) << 1) | ((x & 0xAAU) >> 1));
    x = (mx_uint8_t)(((x & 0x33U) << 2) | ((x & 0xCCU) >> 2));
    x = (mx_uint8_t)((x << 4) | (x >> 4));
    return x;
}

mx_uint8_t hash_crc8 (hash_crc8cfg_pt pcfg, mx_uint8_pt data, mx_usize_t len)
{
    mx_uint8_t crc = pcfg->init;
    for (mx_usize_t i = 0; i < len; i++)
    {
        mx_uint8_t byte = data[i];
        if (pcfg->refin)
            byte = reflect8(byte);
        crc ^= byte;
        for (mx_usize_t j = 0; j < 8; j++)
        {
            if (crc & 0x80U)
                crc = (mx_uint8_t)((crc << 1) ^ pcfg->poly);
            else
                crc <<= 1;
        }
    }
    if (pcfg->refout)
        crc = reflect8(crc);
    crc ^= pcfg->xorout;
    return crc;
}

static mx_inline mx_uint16_t reflect16 (mx_uint16_t x)
{
    x = (mx_uint16_t)(((x & 0x5555U) << 1) | ((x & 0xaaaaU) >> 1));
    x = (mx_uint16_t)(((x & 0x3333U) << 2) | ((x & 0xccccU) >> 2));
    x = (mx_uint16_t)(((x & 0x0f0fU) << 4) | ((x & 0xf0f0U) >> 4));
    x = (mx_uint16_t)((x << 8) | (x >> 8));
    return x;
}

mx_uint16_t hash_crc16 (hash_crc16cfg_pt cfg, mx_uint8_pt data, mx_usize_t len)
{
    mx_uint16_t crc = cfg->init;
    for (mx_usize_t i = 0; i < len; i++)
    {
        mx_uint8_t byte = data[i];
        if (cfg->refin)
            byte = reflect8(byte);
        crc ^= ((mx_uint16_t)byte << 8);
        for (mx_usize_t j = 0; j < 8; j++)
        {
            if (crc & 0x8000U)
                crc = (mx_uint16_t)((crc << 1) ^ cfg->poly);
            else
                crc <<= 1;
        }
    }
    if (cfg->refout)
        crc = reflect16(crc);
    crc ^= cfg->xorout;
    return crc;
}

static mx_inline mx_uint32_t reflect32 (mx_uint32_t x)
{
    x = ((x & 0x55555555U) << 1) | ((x & 0xaaaaaaaaU) >> 1);
    x = ((x & 0x33333333U) << 2) | ((x & 0xccccccccU) >> 2);
    x = ((x & 0x0f0f0f0fU) << 4) | ((x & 0xf0f0f0f0U) >> 4);
    x = ((x & 0x00ff00ffU) << 8) | ((x & 0xff00ff00U) >> 8);
    x = (x << 16) | (x >> 16);
    return x;
}

mx_uint32_t hash_crc32 (hash_crc32cfg_pt cfg, mx_uint8_pt data, mx_usize_t len)
{
    mx_uint32_t crc = cfg->init;
    for (mx_usize_t i = 0; i < len; i++) {
        mx_uint8_t byte = data[i];
        if (cfg->refin)
            byte = reflect8(byte);
        crc ^= ((mx_uint32_t)byte << 24);
        for (mx_usize_t j = 0; j < 8; j++)
        {
            if (crc & 0x80000000U)
                crc = (crc << 1) ^ cfg->poly;
            else
                crc <<= 1;
        }
    }
    if (cfg->refout)
        crc = reflect32(crc);
    crc ^= cfg->xorout;
    return crc;
}
